﻿//
//  Monkey Manager
//  Copyright (C) 2012 Victor Tereschenko
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//     
// ========================================================================

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace ManagerDB
{
    /// <summary>
    /// Reads description from XML file.
    /// </summary>
    public class DescriptionFileReader
    {
        /// <summary>
        /// Exception throw when reading descrion with errors in formatting.
        /// </summary>
        public class IncorrectFormatExceotion : Exception
        {
        }

        /// <summary>
        /// Reads file from XML file.
        /// </summary>
        /// <param name="path"></param>
        /// <exception cref="IncorrectFormatExceotion">In case of incorrect format of description file.</exception>
        /// <returns></returns>
        public static StoredFile Read(string path)
        {
            var doc = new XmlDocument();
            try
            {
                doc.Load(path);
            }
            catch (Exception)
            {
                throw new IncorrectFormatExceotion();
            }
            var descNode = doc.SelectSingleNode("desc");
            if (descNode == null)
                throw new IncorrectFormatExceotion();

            var description = read(System.IO.Path.GetDirectoryName(path), descNode);
            return description;
        }

        /// <summary>
        /// Reads file from description.
        /// </summary>
        /// <param name="descpriptionNode"></param>
        /// <returns></returns>
        private static StoredFile read(string path, XmlNode descpriptionNode)
        {
            StoredFile file = new StoredFile();
            DateTime? date = readDate(descpriptionNode, "date");
            if (date.HasValue)
            {
                file.Year = date.Value.Year;
                file.Month = date.Value.Month;
                file.Day = date.Value.Day;
            }
            file.Title = readString(descpriptionNode, "name");
            file.Original = readString(descpriptionNode, "original_name");
            file.Description = readString(descpriptionNode, "description");
            file.Tags = readStrings(descpriptionNode, "tags//tag");
            if (file.Tags == null)
                file.Tags = new List<string>();
            file.Images.AddRange(readImages(descpriptionNode));
            var files = readFiles(descpriptionNode);
            if (files.Length == 0)
                return file;
            foreach (FileDescription fileDesc in files)
            {
                file.Files.AddRange(files);
            }

            return file;
        }

        static FileID[] readImages(XmlNode descpriptionNode)
        {
            var filesNode = descpriptionNode.SelectSingleNode("covers");
            if (filesNode == null)
                return new FileID[0];
            var fileNodes = filesNode.SelectNodes("cover");
            if (fileNodes == null)
                return new FileID[0];
            List<FileID> files = new List<FileID>();
            foreach (XmlNode fileNode in fileNodes)
            {
                var file = readImage(fileNode);
                if (file != null)
                    files.Add(file.ID);
            }

            return files.ToArray();
        }

        static FileDescription readImage(XmlNode node)
        {
            if (node.Attributes["TTH"] == null || node.Attributes["TTH"].Value == "")
                return null;
            long size;
            if (node.Attributes["size"] == null || !long.TryParse(node.Attributes["size"].Value, out size))
                return null;

            var file = new FileDescription();
            file.ID.TTH = node.Attributes["TTH"].Value;
            file.ID.Size = size;
            return file;
        }

        /// <summary>
        /// Reads files.
        /// </summary>
        /// <param name="descpriptionNode"></param>
        /// <returns></returns>
        static FileDescription[] readFiles(XmlNode descpriptionNode)
        {
            var filesNode = descpriptionNode.SelectSingleNode("files");
            if (filesNode == null)
                return new FileDescription[0];
            var fileNodes = filesNode.SelectNodes("file");
            if (fileNodes == null)
                return new FileDescription[0];
            List<FileDescription> files = new List<FileDescription>();
            foreach (XmlNode fileNode in fileNodes)
            {
                var file = readFile(fileNode);
                if (file != null)
                    files.Add(file);
            }

            return files.ToArray();
        }

        /// <summary>
        /// Reads file description from node.
        /// </summary>
        /// <param name="node"></param>
        /// <returns>File description or null in case of incorrect node.</returns>
        static FileDescription readFile(XmlNode node)
        {
            if (node.Attributes["TTH"] == null || node.Attributes["TTH"].Value == "")
                return null;
            long size;
            if (node.Attributes["size"] == null || !long.TryParse(node.Attributes["size"].Value, out size))
                return null;

            var file = new FileDescription();
            file.ID.TTH = node.Attributes["TTH"].Value;
            file.ID.Size = size;
            if (node.InnerText != null)
                file.TechInfo = node.InnerText;
            return file;
        }

        /// <summary>
        /// Reads date from description by node name.
        /// </summary>
        /// <param name="descpriptionNode"></param>
        /// <returns>Date or null in case of absent date or incorrect format.</returns>
        private static DateTime? readDate(XmlNode descpriptionNode, string nodeName)
        {
            var date = descpriptionNode.SelectSingleNode(nodeName);
            if (date == null)
                return null;

            DateTime parsedDate = new DateTime();
            if (!DateTime.TryParse(date.InnerText, out parsedDate))
                return null;

            return parsedDate;
        }

        /// <summary>
        /// Reads string by node name.
        /// </summary>
        /// <param name="descpriptionNode"></param>
        /// <param name="name">Node value or null.</param>
        /// <returns></returns>
        private static string readString(XmlNode descpriptionNode, string name)
        {
            var node = descpriptionNode.SelectSingleNode(name);
            if (node == null)
                return null;

            return node.InnerText;
        }

        /// <summary>
        /// Reads list of string by xpath. Only unique items will be returned.
        /// </summary>
        /// <param name="descpriptionNode"></param>
        /// <param name="xpath"></param>
        /// <returns>List of string. null in case of 0 items.</returns>
        private static List<string> readStrings(XmlNode descpriptionNode, string xpath)
        {
            var nodes = descpriptionNode.SelectNodes(xpath);
            if (nodes == null)
                return null;

            var values = new List<string>();
            foreach (XmlNode node in nodes)
            {
                var value = node.InnerText;
                if (!values.Exists(s => s == value))
                    values.Add(value);
            }
            return values;
        }
    }
}
